package org.dding.msc.service;

import org.dding.msc.exception.ServiceLifecycException;
import org.dding.msc.service.management.ServiceStatus;
import org.dding.msc.value.Value;

/**
 * A controller for a single service instance.
 * 
 * @param <S>
 *            the service type
 * 
 */
public interface ServiceController<S> extends Value<S> {

	/**
	 * Get the service container associated with this controller.
	 * 
	 * @return the container
	 */
	ServiceContainer getServiceContainer();

	/**
	 * Get the service controller's current mode.
	 * 
	 * @return the controller mode
	 */
	Mode getMode();

	/**
	 * Compare the current mode against {@code expected}; if it matches, change
	 * it to {@code newMode}. The return value is {@code true} when the mode was
	 * matched and changed.
	 * 
	 * @param expected
	 *            the expected mode
	 * @param newMode
	 *            the new mode
	 * @return {@code true} if the mode was changed
	 */
	boolean compareAndSetMode(Mode expected, Mode newMode);

	/**
	 * Change the service controller's current mode. Might result in the service
	 * starting or stopping. The mode may only be changed if it was not already
	 * set to {@link Mode#REMOVE}. Calling this method with the controller's
	 * current mode has no effect and is always allowed.
	 * 
	 * @param mode
	 *            the new controller mode
	 * @throws IllegalStateException
	 *             if the mode given is {@code null}, or the caller attempted to
	 *             change the service's mode from {@link Mode#REMOVE} to a
	 *             different mode
	 */
	void setMode(Mode mode);

	/**
	 * Get the current service controller state.
	 * 
	 * @return the current state
	 */
	State getState();

	/**
	 * Get the service value.
	 * 
	 * @return the service value
	 * @throws IllegalStateException
	 *             if the service is not available (i.e. it is not up)
	 */
	S getValue() throws IllegalStateException;

	/**
	 * Get the service.
	 * 
	 * @return the service
	 * @throws IllegalStateException
	 *             if the service is not available (i.e. it is not up)
	 */
	Service<S> getService() throws IllegalStateException;

	/**
	 * Get the name of this service, if any.
	 * 
	 * @return the name, or {@code null} if none was specified.
	 */
	ServiceName getName();

	/**
	 * Get other names this service is known as.
	 * 
	 * @return the aliases
	 */
	ServiceName[] getAliases();

	/**
	 * Add a service listener. The method corresponding to the current service
	 * state is called.
	 * 
	 * @param serviceListener
	 *            the service listener
	 */
	void addListener(ServiceListener<? super S> serviceListener);

	/**
	 * Add a service listener. The method corresponding to the current service
	 * state is called.
	 * 
	 * @param inheritance
	 *            the inheritance type for this listener
	 * @param serviceListener
	 *            the service listener
	 */
	void addListener(ServiceListener.Inheritance inheritance,
			ServiceListener<Object> serviceListener);

	/**
	 * Remove a service listener.
	 * 
	 * @param serviceListener
	 *            the service listener to remove
	 */
	void removeListener(ServiceListener<? super S> serviceListener);

	/**
	 * Get the reason why the last start failed.
	 * 
	 * @return the last lifecyc exception, or {@code null} if the last start
	 *         succeeded or the service has not yet started
	 */
	ServiceLifecycException getServiceLifecycException();

	/**
	 * Retry a failed service. Does nothing if the state is not
	 * {@link State#START_FAILED}.
	 */
	void retry();

	ServiceStatus getStatus();

	void setState(State targetState) throws ServiceLifecycException;
	
	Command getCommand();

	/**
	 * A possible state for a service controller.
	 */
	/**
	 * The controller mode for a service.
	 */
	public enum Mode {

		/**
		 * Remove this service and all of its dependents.
		 */
		REMOVE,
		/**
		 * Do not start; in addition, ignore demands from dependents.
		 */
		NEVER,
		/**
		 * Only come up if all dependencies are satisfied <b>and</b> at least
		 * one dependent demands to start.
		 */
		ON_DEMAND,
		/**
		 * Come up automatically as soon as all dependencies are satisfied.
		 */
		ACTIVE, ;
	}

	/**
	 * A fine-grained substate of the more general basic controller
	 * {@link State}s. The list of possible substates may change over time, so
	 * users should not rely on its permanence.
	 */
	enum State {
		NEW("NEW", false),
		/**
		 * Second phase of start processing ({@link Service#start(StartContext)
		 * start()} method invoked).
		 */
		STARTING("STARTING", false),
		/**
		 * Service is up.
		 */
		UP("UP", true),
		/**
		 * Service is stopping.
		 */
		STOPPING("STOPPING", false),
		/**
		 * Controller is waiting for an external condition to start, such as a
		 * dependent demand.
		 */
		WAITING("WAITING", true),
		/**
		 * Controller cannot start due to a problem with a dependency or
		 * transitive dependency.
		 */
		PROBLEM("PROBLEM", true),
		/**
		 * Controller is down.
		 */
		DOWN("DOWN", false),

		REMOVE("REMOVE", false);

		private final String state;
		private final boolean restState;

		State(final String state, final boolean restState) {
			this.state = state;
			this.restState = restState;
		}

		/**
		 * Determine whether this is a "rest" state.
		 * 
		 * @return {@code true} if it is a rest state, {@code false} otherwise
		 */
		public boolean isRestState() {
			return restState;
		}

		/**
		 * Get the state corresponding to this sub-state.
		 * 
		 * @return the state
		 */
		public String getState() {
			return state;
		}
	}

	/**
	 * A transition from one substate to another. The list of possible
	 * transitions may change over time, so users should not rely on its
	 * permanence.
	 */
	enum Transition {

		ERROR(State.DOWN, State.DOWN),

		NEW_to_STARTING(State.NEW, State.STARTING),

		STARTING_to_UP(State.STARTING, State.UP),

		UP_to_STOPPING(State.UP, State.STOPPING),

		STOPPING_to_DOWN(State.STOPPING, State.DOWN),

		STARTING_to_PROBLEM(State.STARTING, State.PROBLEM),

		PROBLEM_to_STARTING(State.PROBLEM, State.STARTING),

		STOPPING_to_PROBLEM(State.STOPPING, State.PROBLEM),

		PROBLEM_to_STOPPING(State.PROBLEM, State.STOPPING),

		UP_to_WAITING(State.UP, State.WAITING),

		WAITING_to_UP(State.WAITING, State.UP),

		WAITING_to_STOPPING(State.WAITING, State.STOPPING),

		PROBLEM_to_DOWN(State.PROBLEM, State.DOWN),

		DOWN_to_REMOVE(State.DOWN, State.REMOVE);

		private final State before;
		private final State after;

		Transition(final State before, final State after) {
			this.before = before;
			this.after = after;
		}

		/**
		 * Determine whether this transition causes movement from a rest state
		 * to a non-rest state.
		 * 
		 * @return {@code true} if this transition leaves a rest state
		 */
		public boolean leavesRestState() {
			return before.isRestState() && !after.isRestState();
		}

		/**
		 * Determine whether this transition causes movement from a non-rest
		 * state to a rest state.
		 * 
		 * @return {@code true} if this transition enters a rest state
		 */
		public boolean entersRestState() {
			return !before.isRestState() && after.isRestState();
		}

		/**
		 * Get the source state of this transition.
		 * 
		 * @return the source state
		 */
		public State getBefore() {
			return before;
		}

		/**
		 * Get the target (new) state of this transition.
		 * 
		 * @return the target state
		 */
		public State getAfter() {
			return after;
		}

		/**
		 * Get the string representation of this transition.
		 * 
		 * @return the string
		 */
		public String toString() {
			return "transition from " + before.name() + " to " + after.name();
		}

		public static Transition mapping(State currentState, State targetState) {
			for (Transition transition : values()) {
				if (transition.getBefore().equals(currentState)
						&& transition.getAfter().equals(targetState)) {
					return transition;
				}
			}
			return ERROR;
		}
	}
}
